project('librsvg',
        'c',
        version: '2.59.2', # Keep this in sync with Cargo.toml, doc/librsvg.toml, rsvg/Cargo.toml
        meson_version: '>= 1.3.0',
        default_options: [
          'pkgconfig.relocatable=true',
        ]
)

librsvg_api_version = '2.0'
api_split = librsvg_api_version.split('.')
librsvg_api_major = api_split[0]
librsvg_api_minor = api_split[1]

librsvg_pc = 'librsvg-@0@'.format(librsvg_api_version)
rsvg_ver = 'rsvg-@0@'.format(librsvg_api_major)

host_system = host_machine.system()
cc = meson.get_compiler('c')

# MSRV - Minimum Supported Rust Version
# If you change this, please update these:
#   - the "rust-version" value in Cargo.toml
#   - the "_manual_setup" section of devel-docs/devel_environment.rst
#   - the "RUST_MINIMUM" variable in ci/container_builds.yml
msrv = '1.77.2'
cargo = find_program('cargo', version:'>= @0@'.format(msrv))
cargo_wrapper = find_program('meson/cargo_wrapper.py', native: true)
cargo_c = find_program('cargo-cbuild', version:'>= 0.9.19') # https://github.com/lu-zero/cargo-c
rustc = find_program('rustc', version:'>= @0@'.format(msrv))
makedef = find_program('meson/makedef.py', native: true)

if host_system in ['darwin', 'ios']
  # Validate unconditional presence of getentropy and CCRandomGenerateBytes.
  # https://github.com/rust-lang/rust/pull/116319
  ret = cc.compiles('''#include <Availability.h>
#include <TargetConditionals.h>
#if !((TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200L) || (TARGET_OS_IOS && __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000L))
# error "https://gitlab.gnome.org/GNOME/librsvg/-/issues/1097"
#endif''',
    name: 'Targets at least macOS 10.12 or iOS 10',
  )
  if rustc.version().version_compare('>= 1.75.0') and not ret
    error('When using rustc >=1.75, you must target at least macOS 10.12 or iOS 10')
  endif
endif

py = import('python')

python = py.find_installation()

# Required versions for dependencies - Please update devel_environment.rst with version numbers if these change
cairo_required         = '>= 1.18.0'
dav1d_required         = '>= 1.3.0'
ft2_cmake_required     = '>= 2.8.0'   # Actual FreeType version required
freetype2_required     = '>= 20.0.14' # Corresponds to ft2_cmake_required
gio_required           = '>= 2.24.0'
glib_required          = '>= 2.50.0'
harfbuzz_required      = '>= 2.0.0'
libxml_required        = '>= 2.9.0'
pango_required         = '>= 1.50.0'

# Optional dependencies
gdk_pixbuf_required    = '>= 2.20'
gidocgen_required      = '>= 2021.1'
introspection_required = '>= 1.39.0'
vapigen_required       = '>= 0.17.1.26'
# FIXME: add subprojects for the deps above

cairo_dep      = dependency('cairo',
                            version: cairo_required,
                            fallback: ['cairo', 'libcairo_dep'])
cairogobj_dep  = dependency('cairo-gobject',
                            version: cairo_required,
                            fallback: ['cairo', 'libcairogobject_dep'])
cairo_png_dep  = dependency('cairo-png',
                            version: cairo_required,
                            fallback: ['cairo']) # FIXME: cairo has no _dep for this one
dav1d_dep      = dependency('dav1d',
                            version: dav1d_required,
                            method: 'pkg-config',
                            required: get_option('avif'))
freetype2_dep  = dependency('freetype2',
                            version: freetype2_required,
                            method: 'pkg-config',
                            required: false)

# Try again using CMake, as Windows builds of FreeType might not
# come with pkg-config files; sadly, pkg-config and CMake use
# different version schemes for FreeType
if not freetype2_dep.found()
  freetype2_dep  = dependency('FreeType',
                              version: ft2_cmake_required,
                              method: 'cmake',
                              fallback: ['freetype2', 'freetype_dep'])
endif
pixbuf_dep     = dependency('gdk-pixbuf-2.0',
                            version: gdk_pixbuf_required,
                            required: get_option('pixbuf'),
                            fallback: ['gdk-pixbuf', 'gdkpixbuf_dep'],
                            default_options: ['png=enabled', 'jpeg=enabled', 'builtin_loaders=png,jpeg', 'man=false'])
gio_dep        = dependency('gio-2.0',
                            version: gio_required,
                            fallback: ['glib', 'libgio_dep'])
glib_dep       = dependency('glib-2.0',
                            version: glib_required,
                            fallback: ['glib', 'libglib_dep'])
harfbuzz_dep   = dependency('harfbuzz',
                            version: harfbuzz_required,
                            fallback: ['harfbuzz', 'libharfbuzz_dep'])
libxml_dep     = dependency(['libxml-2.0', 'LibXml2'],
                            version: libxml_required)
pango_dep      = dependency('pango',
                            version: pango_required,
                            fallback: ['pango', 'libpango_dep'])
pangocairo_dep = dependency('pangocairo',
                            version: pango_required,
                            fallback: ['pango', 'libpangocairo_dep'])
pangoft2_dep   = dependency('pangoft2',
                            version: pango_required,
                            required: host_system not in ['windows', 'darwin', 'ios'],
                            fallback: ['pango', 'libpangoft2_dep'])
gmodule_dep    = dependency('gmodule-2.0', version: glib_required,
                            fallback : ['glib', 'libgmodule_dep'])
rst2man        = find_program('rst2man', 'rst2man.py',
                              required: get_option('docs'),
                              disabler: true)
gidocgen       = find_program('gi-docgen', version: gidocgen_required,
                            required: false,
                            disabler: true)
gi_dep         = dependency('gobject-introspection-1.0',
                            version: introspection_required,
                            required: get_option('introspection'))
vapigen_dep    = dependency('vapigen',
                            version: vapigen_required,
                            required: get_option('vala'))

# Extra env to pass to cargo
extra_env = environment()

# If FreeType and/or libxml2 is/are found by CMake instead of
# pkg-config, we must tell Cargo the libraries explicitly and
# disable pkg-config for these deps
if freetype2_dep.type_name() == 'cmake' or libxml_dep.type_name() == 'cmake'
  fs = import('fs')
endif
if freetype2_dep.type_name() == 'cmake'
  ft2_lib = freetype2_dep.get_variable('FREETYPE_LIBRARIES')
  extra_env.set('SYSTEM_DEPS_FREETYPE2_NO_PKG_CONFIG', '1')
  extra_env.set('SYSTEM_DEPS_FREETYPE2_SEARCH_NATIVE', fs.parent(ft2_lib))
  extra_env.set('SYSTEM_DEPS_FREETYPE2_LIB', fs.stem(ft2_lib))
endif
if libxml_dep.type_name() == 'cmake'
  libxml_lib = libxml_dep.get_variable('LIBXML2_LIBRARIES')
  extra_env.set('SYSTEM_DEPS_LIBXML2_NO_PKG_CONFIG', '1')
  extra_env.set('SYSTEM_DEPS_LIBXML2_SEARCH_NATIVE', fs.parent(libxml_lib))
  extra_env.set('SYSTEM_DEPS_LIBXML2_LIB', fs.stem(libxml_lib))
endif

if host_system == 'windows'
  build_gir = get_option('introspection').require(get_option('default_library') != 'static' and not meson.is_cross_build() and gi_dep.found())
else
  build_gir = get_option('introspection').require(not meson.is_cross_build() and gi_dep.found())
endif
build_vala = get_option('vala').require(not meson.is_cross_build() and vapigen_dep.found())

build_pixbuf_loader = get_option('pixbuf-loader').require(pixbuf_dep.found())

build_docs = get_option('docs')

build_tests = get_option('tests')

gnome = import('gnome')

foundation_dep = dependency('appleframeworks', modules: 'Foundation',
                            required: host_system in ['darwin', 'ios'])
m_dep = cc.find_library('m', required: false)

# Use this in place of libxml2/freetype2 for 'Requires:' in the
# pkg-config file in case either or both were found with CMake
empty_pc_dep = dependency('', required: false)

# ignore the system dependencies for GIR
library_dependencies_sole = [
  cairo_dep,
  cairogobj_dep,
  cairo_png_dep,
  dav1d_dep,
  freetype2_dep.type_name() != 'cmake' ? freetype2_dep : empty_pc_dep,
  pixbuf_dep,
  gio_dep,
  glib_dep,
  harfbuzz_dep,
  libxml_dep.type_name() != 'cmake' ? libxml_dep : empty_pc_dep,
  pangocairo_dep,
  pangoft2_dep,
  gmodule_dep,
]

library_dependencies = library_dependencies_sole
private_dependencies = []
other_library_dependencies = []

foreach d: [libxml_dep, freetype2_dep]
  if d.type_name() == 'cmake'
    other_library_dependencies += d
  endif
endforeach

target = get_option('triplet')
target_arg = []

toolchain_arg = []
toolchain_ver = get_option('rustc-version')

check_version = false
# Check toolchain_ver if supplied, if in the form of a version
# We assume to continue if a stable, nightly or beta or custom toolchain is specified
if toolchain_ver != ''
  if host_system == 'windows'
    warning('\'rustc-version\' option currently only supported for Windows')
  endif
  check_version_script = find_program('meson/check-is-version-string.py', native: true)
  is_version_string_check = run_command(
    [check_version_script, '--string', toolchain_ver],
    capture: true,
    check: true
  )
  check_version = is_version_string_check.stdout().split()[0] == 'check'
  if check_version
    if not toolchain_ver.version_compare('>=@0@'.format(msrv))
      error('Specified Rust toolchain version @0@ is less than @1@'.format(toolchain_ver, msrv))
    endif
  endif
endif

# Ideally, cc.get_argument_syntax() == 'msvc' should
# cover this, but 'clang' can mean GCC or MSVC-style
# depending on the env and/or flags on Windows
is_msvc_style = cc.get_argument_syntax() == 'msvc' or cc.get_define('_MSC_VER') != ''

if target != ''
  target_arg += ['--target', target]
endif

if host_system == 'windows'
  query_rustc_harness = find_program('meson/query-rustc.py', native: true, required: get_option('default_library') != 'shared')

  rustc_query_args = [
    query_rustc_harness,
    rustc,
  ]

  # First deduce the target that is to be used for the build, based on Meson build settings
  host_cpu_family = host_machine.cpu_family()
  rustc_toolchain_cpu = host_cpu_family == 'x86' ? 'i686' : host_cpu_family
  if is_msvc_style
    rustc_toolchain_cc = 'msvc'
  elif cc.get_id() == 'clang'
    rustc_toolchain_cc = 'gnullvm'
  else
    rustc_toolchain_cc = 'gnu'
  endif
  rustc_target = '@0@-pc-windows-@1@'.format(rustc_toolchain_cpu, rustc_toolchain_cc)

  if toolchain_ver != ''
    build_triplet_arg = []
    if target_arg == []
      target_arg += ['--target', rustc_target]
    endif

    # If doing a cross build, with '-Drustc-version=...' we must find the native toolchain on the build
    # machine with its arch triplet that is to be used for building against the target, as Cargo will
    # require this in its command line
    if meson.is_cross_build()
      build_cpu_family = build_machine.cpu_family()
      rustc_build_toolchain_cpu = build_cpu_family == 'x86' ? 'i686' : build_cpu_family
      rustc_build_triplet = '@0@-pc-windows-@1@'.format(rustc_build_toolchain_cpu, rustc_toolchain_cc)
      build_triplet_arg += ['--build-triplet', rustc_build_triplet]
    endif

    # If a version number is used, also check against the actual release that is depicted
    # by the currently installed 'stable' toolchain, and use the stable toolchain if the requested
    # version matches the installed 'stable' toolchain
    if check_version
      stable_toolchain_arg = ['--toolchain-version', 'stable']

      stable_actual_version = run_command(
        rustc_query_args + ['--query=stable-actual-version'] + stable_toolchain_arg + target_arg + build_triplet_arg,
        capture: true,
        check: true
      )
      if stable_actual_version.stderr() != ''
        error('error occurred when querying stable toolchain: @0@'.format(default_host.stderr().split()[0]))
      endif
      stable_version = stable_actual_version.stdout().split()[0]
      if stable_version == toolchain_ver
        toolchain_arg += stable_toolchain_arg
      endif
    endif
    if toolchain_arg == []
      toolchain_arg += ['--toolchain-version', toolchain_ver]
    endif
    toolchain_arg += toolchain_arg + build_triplet_arg
  endif

  if target_arg == []
    default_host = run_command(
      rustc_query_args + ['--query=default-host-toolchain'] + toolchain_arg,
      capture: true,
      check: true
    )
    if default_host.stderr() != ''
      error('error occurred when querying default toolchain: @0@'.format(default_host.stderr().split()[0]))
    endif
    default_rustc_toolchain = default_host.stdout().split()[0]

    # If the default Rust target triplet does not match the triplet that we want to build for
    # pass in a --target argument to RustC/Cargo that best matches what we are building for,
    # if we didn't use -Dtriplet=... in the Meson setup line.  Do the same if a toolchain version
    # is requested
    if default_rustc_toolchain != rustc_target
      target_arg += ['--target', rustc_target]
    endif
  endif

  rustc_query_native_static_libs_args = rustc_query_args
  if target_arg != []
    rustc_query_native_static_libs_args += target_arg
  endif
  if toolchain_arg != []
    rustc_query_native_static_libs_args += toolchain_arg
  endif
  rustc_query_native_static_libs_args += ['--query=native-static-libs']

  native_libs = run_command(
    rustc_query_native_static_libs_args,
    capture: true,
    check: true
  )

  if native_libs.stderr() != ''
    error(native_libs.stderr())
  endif

  foreach i: native_libs.stdout().split()
    if i != 'msvcrt'
      private_dependencies += cc.find_library(i, required: get_option('default_library') != 'shared')
    endif
  endforeach
endif

private_dependencies += [m_dep, foundation_dep]
library_dependencies += other_library_dependencies
library_dependencies += private_dependencies

cargo_toml = meson.project_source_root() / 'Cargo.toml'

if get_option('default_library') == 'static'
  # Tell the pkg-config crate to think of all libraries as static
  extra_env.set('PKG_CONFIG_ALL_STATIC', '1')
  # Tell the system-deps crate to process linker flag for static deps
  extra_env.set('SYSTEM_DEPS_LINK', 'static')
  # Allow cross-compilation
  extra_env.set('PKG_CONFIG_ALLOW_CROSS', 'static')
endif

pkg_config = find_program('pkgconfig', 'pkg-config', 'pkgconf', required: true)
extra_env.set('PKG_CONFIG', pkg_config.full_path())

pkg_config_path = get_option('pkg_config_path')
if pkg_config_path.length() > 0
  extra_env.set('PKG_CONFIG_PATH', pkg_config_path)
endif

# Make sure dependencies provided by the superproject are propagated
# (or ours, if there are wraps within this tree)
if meson.is_subproject()
  extra_env.prepend('PKG_CONFIG_PATH', meson.global_build_root() / 'meson-uninstalled')
endif

# Set up the environment that will be fed to the build
if host_system in ['darwin', 'ios']
  var = 'DYLD_LIBRARY_PATH'
elif host_system in ['windows', 'cygwin']
  var = 'PATH'
else
  var = 'LD_LIBRARY_PATH'
endif

current_library_path = run_command(
  [
    python,
    '-c',
    'import os; print(os.environ["@0@"]) if "@0@" in os.environ else ""'.format(var)
  ],
  capture: true,
  check: true,
)

extra_env.set(var, current_library_path.stdout().strip())
extra_env.prepend(var, meson.project_build_root() / 'rsvg')
if host_system == 'windows'
  foreach i: library_dependencies_sole
    x = i.get_variable(pkgconfig: 'bindir', default_value: '')
    if x != ''
      extra_env.prepend('PATH', x)
    endif
  endforeach
endif

# used in librsvg/meson.build.  This is so that '#include <librsvg/rsvg.h>` will pick
# up exactly that file from the source tree.
includeinc = include_directories('include')

# Set the suffixes up
if host_system == 'windows'
  lib_prefix = is_msvc_style ? '' : 'lib'
  ext_dynamic = 'dll'
  ext_static = is_msvc_style ? 'lib' : 'a'
  ext_exe = '.exe'
elif host_system in ['darwin', 'ios']
  lib_prefix = 'lib'
  ext_dynamic = 'dylib'
  ext_static = 'a'
  ext_exe = ''
else
  lib_prefix = 'lib'
  ext_dynamic = 'so'
  ext_static = 'a'
  ext_exe = ''
endif

cargo_dylib_prefix = lib_prefix
if cc.has_define('__MINGW32__')
  cargo_dylib_prefix = ''
endif

cargo_wrapper_args = [
  '--cargo', cargo.full_path(),
  '--manifest-path', cargo_toml,
  '--project-build-root', meson.project_build_root(),
  '--prefix', get_option('prefix'),
  '--libdir', get_option('libdir'),
]

if not get_option('debug') or \
   (is_msvc_style and get_option('buildtype') == 'debugoptimized')
  cargo_wrapper_args += ['--release']
endif

if target_arg != []
  cargo_wrapper_args += target_arg
endif
if toolchain_arg != []
  cargo_wrapper_args += toolchain_arg
endif

# avif support with dav1d

if dav1d_dep.found()
  avif_feature_args = ['--avif']
else
  avif_feature_args = []
endif

# gdk-pixbuf support

if pixbuf_dep.found()
  pixbuf_feature_args = ['--pixbuf']
else
  pixbuf_feature_args = []
endif

subdir('include')
subdir('librsvg-c')
subdir('rsvg')
if build_tests
  subdir('librsvg-c/tests-c')
endif
subdir('rsvg_convert')
if build_pixbuf_loader.allowed()
  subdir('gdk-pixbuf-loader')
endif
if build_docs.allowed()
  subdir('doc')
endif

# The Vala generator must reside in the main folder since Meson isn't yet
# clever enough to take a list of files.
# See https://github.com/mesonbuild/meson/blob/b290a8254122f53d6477b7ba24366d81cfc5e99c/mesonbuild/modules/gnome.py#L2150-L2152
if build_gir.allowed() and build_vala.allowed()
  vala = gnome.generate_vapi(
    'librsvg-@0@'.format(librsvg_api_version),
    packages: vala_includes,
    metadata_dirs: [meson.current_source_dir()],
    sources: ['Rsvg-@0@-custom.vala'.format(librsvg_api_version), rsvg_gir[0]],
    install: true,
  )
endif
